﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Xml.XPath;

namespace ComicsEditor
{
    class Svg
    {
        private static Regex r = new Regex(@"(?<num>[-+.0-9eE]+)|(?<cmd>[MmZzLlHhVvCcSsQqTtAa])");
        private static string format = "0.###";
        private static IFormatProvider provider = System.Globalization.CultureInfo.InvariantCulture.NumberFormat;

        public static GraphicsPath ParsePath(string value)
        {
            int i;

            MatchCollection mc = r.Matches(value);
            List<object> seq = new List<object>(mc.Count);
            foreach (Match m in mc)
                for (i = 0; i < m.Groups.Count; i++)
                    if (m.Groups[i].Success)
                    {
                        if (r.GroupNameFromNumber(i) == "cmd")
                            seq.Add(m.Value);
                        if (r.GroupNameFromNumber(i) == "num")
                            seq.Add(ParseFloat(m.Value));
                    }

            List<PointF> points = new List<PointF>();
            List<byte> types = new List<byte>();
            string nextCmd = "M";
            PointF cur = PointF.Empty;
            i = 0;
            while (i < seq.Count)
            {
                string cmd = nextCmd;
                if (seq[i] is string)
                {
                    cmd = (string)seq[i];
                    nextCmd = cmd;
                    i++;
                }
                switch (cmd)
                {
                    case "M":
                        points.Add(new PointF((float)seq[i], (float)seq[i + 1]));
                        types.Add(0);
                        i += 2;
                        nextCmd = "L";
                        break;
                    case "m":
                        points.Add(new PointF(cur.X + (float)seq[i], cur.Y + (float)seq[i + 1]));
                        types.Add(0);
                        i += 2;
                        nextCmd = "l";
                        break;
                    case "L":
                        points.Add(new PointF((float)seq[i], (float)seq[i + 1]));
                        types.Add(1);
                        i += 2;
                        break;
                    case "l":
                        points.Add(new PointF(cur.X + (float)seq[i], cur.Y + (float)seq[i + 1]));
                        types.Add(1);
                        i += 2;
                        break;
                    case "C":
                        points.Add(new PointF((float)seq[i], (float)seq[i + 1]));
                        types.Add(3);
                        i += 2;
                        points.Add(new PointF((float)seq[i], (float)seq[i + 1]));
                        types.Add(3);
                        i += 2;
                        points.Add(new PointF((float)seq[i], (float)seq[i + 1]));
                        types.Add(3);
                        i += 2;
                        break;
                    case "c":
                        points.Add(new PointF(cur.X + (float)seq[i], cur.Y + (float)seq[i + 1]));
                        types.Add(3);
                        i += 2;
                        points.Add(new PointF(cur.X + (float)seq[i], cur.Y + (float)seq[i + 1]));
                        types.Add(3);
                        i += 2;
                        points.Add(new PointF(cur.X + (float)seq[i], cur.Y + (float)seq[i + 1]));
                        types.Add(3);
                        i += 2;
                        break;
                    case "S":
                        points.Add(new PointF(2*cur.X - points[points.Count - 2].X, 2*cur.Y - points[points.Count - 2].Y));
                        types.Add(3);
                        points.Add(new PointF((float)seq[i], (float)seq[i + 1]));
                        types.Add(3);
                        i += 2;
                        points.Add(new PointF((float)seq[i], (float)seq[i + 1]));
                        types.Add(3);
                        i += 2;
                        break;
                    case "s":
                        points.Add(new PointF(2*cur.X - points[points.Count - 2].X, 2*cur.Y - points[points.Count - 2].Y));
                        types.Add(3);
                        points.Add(new PointF(cur.X + (float)seq[i], cur.Y + (float)seq[i + 1]));
                        types.Add(3);
                        i += 2;
                        points.Add(new PointF(cur.X + (float)seq[i], cur.Y + (float)seq[i + 1]));
                        types.Add(3);
                        i += 2;
                        break;
                    case "Z":
                    case "z":
                        types[types.Count - 1] |= 128;
                        break;
                }
                cur = points[points.Count - 1];
            }

            if (points.Count == 0)
                return new GraphicsPath();
            return new GraphicsPath(points.ToArray(), types.ToArray());
        }

        public static string PathToString(GraphicsPath path)
        {
            if (path.PointCount == 0)
                return "";

            int i, j;
            PointF cur = PointF.Empty;
            StringBuilder sb = new StringBuilder();

            byte[] types = path.PathTypes;
            PointF[] points = path.PathPoints;
            for (i = 0; i < types.Length; i++)
            {
                if (((PathPointType)types[i] & PathPointType.PathTypeMask) == PathPointType.Start)
                    sb.AppendFormat("M{0} {1}", FloatToString(points[i].X), FloatToString(points[i].Y));
                else if (((PathPointType)types[i] & PathPointType.PathTypeMask) == PathPointType.Line)
                    sb.AppendFormat("l{0} {1}", FloatToString(points[i].X - cur.X), FloatToString(points[i].Y - cur.Y));
                else if (((PathPointType)types[i] & PathPointType.PathTypeMask) == PathPointType.Bezier)
                {
                    j = 0;
                    if (i > 2 && ((PathPointType)types[i - 2] & PathPointType.PathTypeMask) == PathPointType.Bezier &&
                    Math.Abs(points[i].X + points[i - 2].X - 2*points[i - 1].X) < 1e-3 &&
                    Math.Abs(points[i].Y + points[i - 2].Y - 2*points[i - 1].Y) < 1e-3)
                    {
                        sb.Append("s");
                        i++;
                        j++;
                    }
                    else
                        sb.Append("c");
                    for (; j < 3; j++, i++)
                        sb.AppendFormat(" {0} {1}", FloatToString(points[i].X - cur.X), FloatToString(points[i].Y - cur.Y));
                    i--;
                }
                if (((PathPointType)types[i] & PathPointType.CloseSubpath) != 0)
                    sb.Append("z");
                cur = points[i];
            }

            return sb.ToString();
        }

        public static Brush ParseFill(XPathNavigator nav)
        {
            string strFill = nav.GetAttribute("fill", "");
            if (strFill != "" && strFill != "none")
            {
                Color color = parseRgb(strFill);
                string opacity = nav.GetAttribute("fill-opacity", "");
                if (opacity != "")
                    color = Color.FromArgb((int)Math.Round(ParseFloat(opacity)*255), color);
                return new SolidBrush(color);
            }
            return null;
        }

        public static Pen ParseStroke(XPathNavigator nav)
        {
            string strStroke = nav.GetAttribute("stroke", "");
            if (strStroke != "" && strStroke != "none")
            {
                float width = 1.0f;
                string strWidth = nav.GetAttribute("stroke-width", "");
                if (strWidth != "")
                    width = ParseFloat(strWidth);
                Color color = parseRgb(strStroke);
                string opacity = nav.GetAttribute("stroke-opacity", "");
                if (opacity != "")
                    color = Color.FromArgb((int)Math.Round(ParseFloat(opacity)*255), color);
                return new Pen(color, width);
            }
            return null;
        }

        private static Color parseRgb(string rgb)
        {
            string[] tokens = rgb.Split(' ', '(', ',', ')');
            if (tokens[0] != "rgb")
                throw new ArgumentException();
            return Color.FromArgb(int.Parse(tokens[1]), int.Parse(tokens[2]), int.Parse(tokens[3]));
        }

        public static string FillToString(Brush fill)
        {
            return FillToString(fill, 255);
        }

        public static string FillToString(Brush fill, byte defaultOpacity)
        {
            StringBuilder sb = new StringBuilder();
            if (fill != null)
            {
                sb.AppendFormat(" fill=\"rgb({0},{1},{2})\"", (fill as SolidBrush).Color.R, (fill as SolidBrush).Color.G, (fill as SolidBrush).Color.B);
                if ((fill as SolidBrush).Color.A != defaultOpacity)
                    sb.AppendFormat(" fill-opacity=\"{0}\"", FloatToString(Math.Round((fill as SolidBrush).Color.A/2.55)/100));
            }
            else
                sb.Append(" fill=\"none\"");
            return sb.ToString();
        }

        public static string StrokeToString(Pen stroke)
        {
            return StrokeToString(stroke, 255);
        }

        public static string StrokeToString(Pen stroke, byte defaultOpacity)
        {
            StringBuilder sb = new StringBuilder();
            if (stroke != null)
            {
                sb.AppendFormat(" stroke=\"rgb({0},{1},{2})\"", stroke.Color.R, stroke.Color.G, stroke.Color.B);
                if (stroke.Color.A != defaultOpacity)
                    sb.AppendFormat(" stroke-opacity=\"{0}\"", FloatToString(Math.Round(stroke.Color.A/2.55)/100));
                sb.AppendFormat(" stroke-width=\"{0}\"", FloatToString(stroke.Width));
            }
            else
                sb.Append(" stroke=\"none\"");
            return sb.ToString();
        }

        public static float ParseFloat(string str)
        {
            return float.Parse(str, provider);
        }

        public static string FloatToString(float f)
        {
            return f.ToString(format, provider);
        }

        public static string FloatToString(double f)
        {
            return f.ToString(format, provider);
        }

        public static int ParseInt(string str)
        {
            return (int)(ParseFloat(str) + 0.5);
        }
    }
}
